#include "AbstractUploader.h"
#include <QIODevice>
#define DEBUG
#include <debug.h>
#define ALLOWED_DEBUG_LEVEL DBG_5

AbstractUploader::AbstractUploader()
		: QObject(), maxParallelThreads(2), imagesAtOnce(1), netManager(new QNetworkAccessManager(this))
{
	uploadWorkersRunning.clear();
	uploadWorkersFinished.clear();
}

AbstractUploader::~AbstractUploader() {
//	netManager->deleteLater(); // MetaObject destructor will do it.
}

bool AbstractUploader::limitAllow() {
	dbg << "limit" << uploadWorkersRunning.count() << maxParallelThreads;
	return ( uploadWorkersRunning.count() < maxParallelThreads
		 or maxParallelThreads == 0 );
}

/*
 * main abstract uploader class
 * process signals and slots
 */

void AbstractUploader::startUpload(QList<ImageToUpload> images, QObject *mainAppSignals_)
{
	this->mainApp = mainAppSignals_;
	this->allImages = images;
	imageIterator = new QListIterator<ImageToUpload>(images);
	requestAborted = false;
	connect(mainApp, SIGNAL(cancelUpload()), this, SLOT(cancelClicked()));
	connect(this, SIGNAL(totalError(QString)), mainApp, SIGNAL(totalError(QString)));
	connect(this, SIGNAL(uploadCompleted(QList<UploadResult>)), mainApp, SIGNAL(uploadComplete(QList<UploadResult>)));

	// Sessions
	connect(this, SIGNAL(authorized()), SLOT(uploadNextFile()));
	connect(this, SIGNAL(authError(QString)), SIGNAL(totalError(QString)));
	authorizeAndUpload();
}

void AbstractUploader::uploadNextFile() {
#define CONDITION imageIterator->hasNext() and !requestAborted and limitAllow()
	if ( CONDITION ) {
		do {
			upload();
		} while ( CONDITION );
	} else
		checkAllDone();
#undef CONDITION
}

void AbstractUploader::upload()
{
	UploadWorker *worker = newUploadWorker();
	uploadWorkersRunning.append(worker);
	connect(worker, SIGNAL(finished()), this, SLOT(workerFinished()));
	connect(worker, SIGNAL(fileUploaded(int,UploadResult)), mainApp, SIGNAL(fileUploaded(int,UploadResult)));
	connect(worker, SIGNAL(fileError(int,QString)), mainApp, SIGNAL(fileUploadFailed(int,QString)));
	connect(worker, SIGNAL(updateStatus(int,int,int)), mainApp, SIGNAL(updateStatus(int,int,int)));
	connect(worker, SIGNAL(startFile(int)), mainApp, SIGNAL(startNextFile(int)));

	int imageCnt = 0;
	while ( (imageCnt++ < imagesAtOnce or imagesAtOnce == 0) and imageIterator->hasNext()) {
		worker->append(imageIterator->next()); // This function is boolean, actually
	}
	worker->run();
}

void AbstractUploader::checkAllDone() {
	if ( this->uploadWorkersRunning.isEmpty() )
		finished();
	else
		dbg << "wait for" << uploadWorkersRunning.size() << "workers";
}

void AbstractUploader::finished() {
	// collect results and emit finish signal
	QList<UploadResult> res;
	dbg << "finished!";
	foreach (UploadWorker* wrk, uploadWorkersFinished) {
		res.append(wrk->allLinks());
		wrk->deleteLater();
	}
	if ( requestAborted ) {
		res.append(cancelLastImages());
	}
	checkLinksCount(res);
	uploadWorkersFinished.clear();
	uploadWorkersRunning.clear();
	delete imageIterator;
	imageIterator = 0;
	emit uploadCompleted(res);
}

void AbstractUploader::checkLinksCount(QList<UploadResult> &res) {
	QHash<int, int> indexes;
	int totalRes = res.size();
	for ( int i = 0; i < totalRes; i++ )
		indexes[res.at(i).index] = i;
	QList<UploadResult> append;
	foreach ( const ImageToUpload &img, allImages ) {
		if ( !indexes.contains(img.index) ) {
			UploadResult tmpres;
			tmpres.index = img.index;
			tmpres.ImageName = img.ImageName;
			tmpres.success = false;
			tmpres.errorString = tr("No links returned");
			append.append(tmpres);
		}
	}
	if (append.size() > 0)
		res.append(append);

}

QList<UploadResult> AbstractUploader::cancelLastImages() {
//	abort last images
	QList<UploadResult> ret;
	while ( imageIterator->hasNext() ) {
		const ImageToUpload &img = imageIterator->next();
		UploadResult tmp;
		tmp.index = img.index;
		tmp.success = false,
		tmp.canceled = true;
		tmp.ImageName = img.ImageName;
		ret.append(tmp);
	}
	return ret;
}

void AbstractUploader::workerFinished() {
	UploadWorker *worker = qobject_cast<UploadWorker*>(QObject::sender());
	if(uploadWorkersRunning.contains(worker)) {
		// delete from running and move to finished
		uploadWorkersRunning.removeAll(worker);
		uploadWorkersFinished.append(worker);
		this->uploadNextFile();
	} else {
		err << __FUNCTION__ << "caller not in uploadWorkersRunning list";
	}
}

void AbstractUploader::cancelClicked()
{
	this->requestAborted = true;
//	abortAllRunning;
	foreach (UploadWorker* worker, uploadWorkersRunning)
		worker->abort();
}




